package org.dromara.hotel.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hotel.domain.TerHotelRooms;
import org.dromara.hotel.domain.TerPricingRule;
import org.dromara.hotel.domain.bo.Inventory;
import org.dromara.hotel.domain.bo.PriceCalendarDTO;
import org.dromara.hotel.mapper.TerHotelRoomsMapper;
import org.dromara.hotel.mapper.TerPricingRuleMapper;
import org.dromara.hotel.service.ITerHotelInventoryService;
import org.dromara.hotel.service.ITerPricingRuleService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.dromara.hotel.domain.bo.TerPriceCalendarBo;
import org.dromara.hotel.domain.vo.TerPriceCalendarVo;
import org.dromara.hotel.domain.TerPriceCalendar;
import org.dromara.hotel.mapper.TerPriceCalendarMapper;
import org.dromara.hotel.service.ITerPriceCalendarService;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 酒店日历价格Service业务层处理
 *
 * @author xch
 * @date 2025-06-10
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class TerPriceCalendarServiceImpl implements ITerPriceCalendarService {

    private final TerPriceCalendarMapper baseMapper;
    private final TerHotelRoomsMapper roomMapper;
    private final TerPricingRuleMapper ruleMapper;
    private final ITerPricingRuleService ruleService;
    private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
    private final RedisTemplate<String, String> redisTemplate;
    /**
     * 查询酒店日历价格
     *
     * @param id 主键
     * @return 酒店日历价格
     */
    @Override
    public TerPriceCalendarVo queryById(Long id){
        return baseMapper.selectVoById(id);
    }

    /**
     * 分页查询酒店日历价格列表
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 酒店日历价格分页列表
     */
    @Override
    public TableDataInfo<TerPriceCalendarVo> queryPageList(TerPriceCalendarBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<TerPriceCalendar> lqw = buildQueryWrapper(bo);
        Page<TerPriceCalendarVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    /**
     * 查询符合条件的酒店日历价格列表
     *
     * @param bo 查询条件
     * @return 酒店日历价格列表
     */
    @Override
    public List<TerPriceCalendarVo> queryList(TerPriceCalendarBo bo) {
        LambdaQueryWrapper<TerPriceCalendar> lqw = buildQueryWrapper(bo);
        if(bo.getEndDate()!=null){
            return queryPriceCalendar(bo.getEndDate(),bo.getRoomId());
        }
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<TerPriceCalendar> buildQueryWrapper(TerPriceCalendarBo bo) {
        LambdaQueryWrapper<TerPriceCalendar> lqw = Wrappers.lambdaQuery();
        if(bo!=null){
            lqw.orderByAsc(TerPriceCalendar::getPriceDay);
            lqw.eq(bo.getRoomId() != null, TerPriceCalendar::getRoomId, bo.getRoomId());
            lqw.eq(bo.getPriceDay() != null, TerPriceCalendar::getPriceDay, bo.getPriceDay());
            lqw.eq(bo.getFinalPrice() != null, TerPriceCalendar::getFinalPrice, bo.getFinalPrice());
            lqw.eq(bo.getSource() != null, TerPriceCalendar::getSource, bo.getSource());
        }
        return lqw;
    }

    /**
     * 新增酒店日历价格
     *
     * @param bo 酒店日历价格
     * @return 是否新增成功
     */
    @Override
    public Boolean insertByBo(TerPriceCalendarBo bo) {
        TerPriceCalendar add = MapstructUtils.convert(bo, TerPriceCalendar.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    /**
     * 修改酒店日历价格
     *
     * @param bo 酒店日历价格
     * @return 是否修改成功
     */
    @Override
    public Boolean updateByBo(TerPriceCalendarBo bo) {
        TerPriceCalendar update = MapstructUtils.convert(bo, TerPriceCalendar.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(TerPriceCalendar entity){
        //TODO 做一些数据校验,如唯一约束
    }
    @Override
    public Boolean inventoryModify(PriceCalendarDTO dto){
            int num=0;
            for(Inventory inventory : dto.getDatas()){
                int update= baseMapper.update(null,
                    new LambdaUpdateWrapper<TerPriceCalendar>()
                        .eq(TerPriceCalendar::getRoomId, dto.getRoomId())
                        .eq(TerPriceCalendar::getPriceDay, inventory.getDates())
                        .set(TerPriceCalendar::getFinalPrice, inventory.getFinalPrice())
                        .set(TerPriceCalendar::getAvailableQuantity, inventory.getAvailableQuantity()));
                num+=update;
            }
            //强同步 db-》redis
            if(num>0)scheduleFullSync();
            return true;
    }
    private void scheduleFullSync() {
        RLock lock = CLIENT.getLock("inventory:full-sync:lock");
        try {
            if (lock.tryLock(10, 60, TimeUnit.SECONDS)) {
                syncAllInventory();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
        }
    }
    @Async
    protected void syncAllInventory() {
        List<TerPriceCalendarVo> inventoryList = this.queryList(null);
        if (CollectionUtils.isEmpty(inventoryList)) return;

        inventoryList.forEach(av -> {
            String key = buildInventoryKey(av.getRoomId(), av.getPriceDay());
            redisTemplate.opsForValue().set(key, String.valueOf(av.getAvailableQuantity()));
        });
    }
    private String buildInventoryKey(Long roomTypeId, LocalDate date) {
        return  "hotel:inventory:{roomTypeId}:{date}" .replace("{roomTypeId}", roomTypeId.toString()).replace("{date}", date.toString());
    }
    /**
     * 校验并批量删除酒店日历价格信息
     *
     * @param ids     待删除的主键集合
     * @param isValid 是否进行有效性校验
     * @return 是否删除成功
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteByIds(ids) > 0;
    }
    @Override
    public Boolean decreaseAvailable(Long roomTypeId, LocalDate date, int num){
        TerPriceCalendarBo bo = new TerPriceCalendarBo();
        bo.setRoomId(roomTypeId);
        bo.setPriceDay(date);
        List<TerPriceCalendarVo> list = queryList(bo);
        if(CollectionUtils.isEmpty(list)){
            return true;
        }
        bo.setAvailableQuantity(list.get(0).getAvailableQuantity()-num);
        bo.setVersion(list.get(0).getVersion()-1);
        return updateByBo(bo);
    }
    @Override
    public Integer queryTotalPrice(TerPriceCalendarBo bo){
        if(isWithinFourMonthRange(bo.getStartDate(), bo.getEndDate())){
            return baseMapper.selectTotalPrice(bo.getRoomId(),bo.getStartDate(),bo.getEndDate());
        }else{
           List<TerPriceCalendarVo> voList= queryPriceCalendar(bo.getEndDate(),bo.getRoomId());
           Integer totalPrice = 0;
           for(TerPriceCalendarVo vo : voList){
               if(!vo.getPriceDay().isBefore(bo.getStartDate())&&!vo.getPriceDay().isAfter(bo.getEndDate())){
                   totalPrice+= vo.getFinalPrice();
               }
           }
           return totalPrice;
        }
    }

    public static boolean isWithinFourMonthRange(LocalDate startDate, LocalDate endDate) {
        // 确定四个月范围的起止日期
        LocalDate startOfRange = LocalDate.now().withDayOfMonth(1);
        LocalDate endOfRange = startOfRange.plusMonths(3).with(TemporalAdjusters.lastDayOfMonth());

        // 判断区间是否完全包含
        return !startDate.isBefore(startOfRange) && !endDate.isAfter(endOfRange);
    }


    private List<TerPriceCalendarVo> queryPriceCalendar( LocalDate end, Long roomTypeId) {

        // 调整为当月的第一天
        LocalDate  newStart = LocalDate.now().withDayOfMonth(1);
        // 调整为当月的最后一天
        LocalDate newEnd = end.with(TemporalAdjusters.lastDayOfMonth());
        LambdaQueryWrapper<TerPricingRule> lqw = Wrappers.lambdaQuery();
        List<TerPriceCalendarVo> allPrices = new ArrayList<>();
        TerHotelRooms room  =roomMapper.selectById(roomTypeId);
        lqw.eq(TerPricingRule::getRoomId, room.getId());
        List<TerPricingRule> activeRules = ruleMapper.selectList(lqw);
        List<TerPriceCalendarVo> prices = IntStream.range(0, (int) ChronoUnit.DAYS.between(newStart, newEnd)+1)
            .mapToObj(offset -> {
                LocalDate date = newStart.plusDays(offset);
                Integer price = ruleService.getDailyPrice(room.getId(), date,activeRules);
                TerPriceCalendarVo tpc= new TerPriceCalendarVo();
                tpc.setRoomId(room.getId());
                tpc.setPriceDay(date);
                tpc.setFinalPrice(price);
                tpc.setAvailableQuantity(room.getTotalQuantity());
                tpc.setSource(1);
                return tpc;
            }).collect(Collectors.toList());
        allPrices.addAll(prices);
        //同步缓存
        allPrices.forEach(av -> {
            String key = buildInventoryKey(av.getRoomId(), av.getPriceDay());
            redisTemplate.opsForValue().set(key, String.valueOf(av.getAvailableQuantity()));
        });

        return allPrices;
    }
}
